package openapi

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"gitee.com/bjf-fhe/apinx/errors"

	"github.com/getkin/kin-openapi/openapi3"
)

type CallingPathArgs interface {
	GetByMethod(method string) *OperationSelection
}

type CallConfig struct {
	Def json.RawMessage
	// Selection         CallingArgs
	ExtraAuthInfos    []*ParameterValue
	EmulatedUrl       string //不经过server选择，直接使用某个baseURL
	Input             interface{}
	NameOfPath        string
	NameOfMethod      string
	DisableSslCheck   bool        //不验证服务器证书
	ShowCertification bool        //将证书内容返回
	Origin            string      //请求的原始地址
	InputType         int         //input模式
	Header            http.Header //用于非标模式下Header数据的存储
	Query             url.Values  //用于非标模式下Query数据的存储
	PathVars          url.Values  //解析url中的变量
	RequestBody       interface{} //用于非标模式下Body数据的存储
	RequestType       string      //用于非标模式下Body数据的存储
	//TODO COOKIES
}

func (args *CallConfig) GetServer(def *openapi3.T, serverId int) (server *openapi3.Server, err error) {
	if args.EmulatedUrl == "" {
		// var serverId = args.Selection.GetServerId()
		if serverId > -1 && serverId < len(def.Servers) {
			server = def.Servers[serverId]
		} else if serverId >= len(def.Servers) {
			err = errors.New(errors.ERR_NO_SUCH_SERVER, fmt.Sprintf("%d", serverId))
		}
	} else {
		server = &openapi3.Server{URL: args.EmulatedUrl}
	}
	return
}

// GetMethod 按调用参数从配置中选择合适的method
// 为了适配更多的调用方式，将原有传递config对象的方式改为传一个path和一个method
func GetMethod(t *openapi3.T, pName, mName string) (method *openapi3.Operation, nameOfPath string, nameOfMethod string, err error) {
	var path *openapi3.PathItem

	// var server *openapi3.Server

	if pName == "" {
		nameOfPath = "/"
	} else {
		nameOfPath = pName
	}

	if mName != "" {
		nameOfMethod = strings.ToLower(mName)
	}

	if t.Paths != nil {
		path = t.Paths.Find(nameOfPath)
		if path == nil {
			//try to use nameOfPath as operationId
			for k, p := range t.Paths.Map() {
				if p.Get != nil && p.Get.OperationID == nameOfPath {
					nameOfMethod = "get"
					method = p.Get
				} else if p.Post != nil && p.Post.OperationID == nameOfPath {
					nameOfMethod = "post"
					method = p.Post
				} else if p.Patch != nil && p.Patch.OperationID == nameOfPath {
					nameOfMethod = "patch"
					method = p.Patch
				} else if p.Put != nil && p.Put.OperationID == nameOfPath {
					nameOfMethod = "put"
					method = p.Put
				} else if p.Delete != nil && p.Delete.OperationID == nameOfPath {
					nameOfMethod = "delete"
					method = p.Delete
				} else if p.Options != nil && p.Options.OperationID == nameOfPath {
					nameOfMethod = "options"
					method = p.Options
				} else if p.Head != nil && p.Head.OperationID == nameOfPath {
					nameOfMethod = "head"
					method = p.Head
				} else if p.Trace != nil && p.Trace.OperationID == nameOfPath {
					nameOfMethod = "trace"
					method = p.Trace
				} else {
					continue
				}
				nameOfPath = k
				path = p
				break
			}
		}
	}

	if path == nil {
		//try to get first
		if t.Paths.Len() == 1 {
			for k, p := range t.Paths.Map() {
				nameOfPath = k
				path = p
			}
		}
	}

	if path != nil {

		if method == nil {

			if nameOfMethod != "" {
				method, err = GetOperation(path, strings.ToUpper(nameOfMethod))
				if err != nil {
					return
				}
			} else {
				//try to get first
				if name, m, ok := methodCountOfPath(path); ok {
					nameOfMethod = name
					method = m
				}
			}
		}

	}

	if method == nil {
		err = errors.New(errors.ERR_NO_SUCH_METHOD)
	}

	return
}

func methodCountOfPath(p *openapi3.PathItem) (name string, m *openapi3.Operation, hasOnly bool) {
	var count = 0
	if p.Get != nil {
		name = "get"
		m = p.Get
		count++
	}
	if p.Post != nil {
		name = "post"
		m = p.Post
		count++
	}
	if p.Patch != nil {
		name = "patch"
		m = p.Patch
		count++
	}
	if p.Put != nil {
		name = "put"
		m = p.Put
		count++
	}
	if p.Delete != nil {
		name = "delete"
		m = p.Delete
		count++
	}
	if p.Options != nil {
		name = "options"
		m = p.Options
		count++
	}
	if p.Head != nil {
		name = "head"
		m = p.Head
		count++
	}
	if p.Trace != nil {
		name = "trace"
		m = p.Trace
		count++
	}
	if count == 1 {
		return name, m, true
	}
	return "", nil, false
}
